package com.hqd.jjvm.rtda.heap.ref;

import com.hqd.jjvm.rtda.heap.JClass;
import com.hqd.jjvm.rtda.heap.JMethod;
import com.hqd.jjvm.rtda.heap.classloader.JClassLoader;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.commons.lang.ArrayUtils;

import java.io.IOException;

@Getter
@Setter
@NoArgsConstructor
public class JMethodRef extends JMemberRef {
    private JMethod jMethod;

    public JMethodRef(JClass jClass, String className, String methodName, String descriptor) throws ClassNotFoundException {
        newJMethodRef(jClass, className, methodName, descriptor);
    }

    public JMethodRef(JClass jClass, JMethodRef methodRef) throws ClassNotFoundException {
        this(jClass, methodRef.getClassName(), methodRef.getName(), methodRef.getDescriptor());
    }

    public JMethodRef(JClass jClass, JMethod jMethod) throws ClassNotFoundException {
        this(jClass, jMethod.getJClass().getClassName(), jMethod.getName(), jMethod.getDescriptor());
    }

    private void newJMethodRef(JClass refJClass, String className, String methodName, String descriptor) throws ClassNotFoundException {
        JClassLoader loader = refJClass.getLoader();
        JClass jc = null;
        try {
            jc = loader.loadJClass(className);
        } catch (IOException e) {
            e.printStackTrace();
        }
        this.setJClass(jc);
        this.setClassName(className);
        this.setName(methodName);
        this.setDescriptor(descriptor);
        this.jMethod = lookupMethod(jc, methodName, descriptor);
        if (jc.isInterface()) {
            //throw new IncompatibleClassChangeError();
        }
        if (jMethod == null) {
            throw new NoSuchMethodError();
        }
        if (!jMethod.isAccessibleTo(refJClass)) {
            throw new IllegalAccessError();
        }
    }

    private JMethod lookupMethod(JClass jc, String methodName, String descriptor) {
        JMethod jMethod = lookupClassMethod(jc, methodName, descriptor);
        if (jMethod == null) {
            jMethod = lookupInterfaceMethod(jc, methodName, descriptor);
        }
        return jMethod;
    }

    private JMethod lookupInterfaceMethod(JClass jc, String methodName, String descriptor) {
        if (ArrayUtils.isNotEmpty(jc.getInterfaceClasses())) {
            JClass[] interfaces = jc.getInterfaceClasses();
            for (JClass jInterface : interfaces) {
                JMethod jMethod = lookupClassMethod(jInterface, methodName, descriptor);
                if (jMethod != null) {
                    return jMethod;
                }
                jMethod = lookupInterfaceMethod(jInterface, methodName, descriptor);
                if (jMethod != null) {
                    return jMethod;
                }
            }
        }
        return null;
    }

    /**
     * 寻找类方法，找不大则找父类方法
     *
     * @param jc
     * @param methodName
     * @param descriptor
     * @return
     */
    private JMethod lookupClassMethod(JClass jc, String methodName, String descriptor) {
        JMethod[] methods = jc.getJMethods();
        for (JMethod method : methods) {
            if (method.getName().equals(methodName) && method.getDescriptor().equals(descriptor)) {
                return method;
            }
        }
        if (jc.getSuperClass() != null) {
            return lookupMethod(jc.getSuperClass(), methodName, descriptor);
        }
        return null;
    }
}
